/*
 * COMP6471 Project
 *  
 * This file is created by Yuan Tao (ewan.msn@gmail.com)
 * Licensed under GNU GPL v3
 * 
 * $Author: ewan.msn@gmail.com $
 * $Date: 2011-11-16 06:13:08 +0000 (Wed, 16 Nov 2011) $
 * $Rev: 60 $
 * $HeadURL: http://comp6471.googlecode.com/svn/Project5/src/kklib/Layout.java $
 * 
 */

package kklib;

import java.util.ArrayList;

import utils.SysLogger;

public class Layout {
	// attributes of the txt file.
	public String fileFullname;
	public ArrayList<String> txtFileData = new ArrayList<String>();
	public ArrayList<String> txtFileFormulas = new ArrayList<String>();	// all the cages formulas in string
	
	// attributes of the layout
	public static final int MAX_GAME_SIZE = 6;
	public String gameName;
	public int gameSize = 4;
	public ArrayList<Cage> cages = new ArrayList<Cage>();
	private int numOfSolutions;			// number of possible solutions
	public static final int MAX_SOLUTIONS = 64;
	
	// to store the cells by connecting them in rows or columns
	public ArrayList<Cell> rows = new ArrayList<Cell>();
	public ArrayList<Cell> columns = new ArrayList<Cell>();
	
	
	// get property
	public int getNumOfSolutions() {
		return numOfSolutions;
	}
	
	// find the cell by its coordinate
	public Cell findCellByXY(int x, int y) {
		for (int i = 0; i < cages.size(); i++) {
			ArrayList<Cell> cells = cages.get(i).cells;
			
			for (int j = 0; j < cells.size(); j++) {
				if (cells.get(j).x == x && cells.get(j).y == y) {
					return cells.get(j);
				}
			}
		}
		
		return null;
	}
	
	// store the cells by rows and columns
	private String getCellTxtCoordinate(int x, int y) {
		return Character.toString((char) ('A' - 1 + y)) + (char) ('0' + x);
	}
	public int sortCells() {
		for (int i = 1; i <= gameSize; i++) {
			for (int j = 1; j <= gameSize; j++) {
				Cell c = findCellByXY(i, j);
				if (c == null) {
					SysLogger.parserErr(fileFullname, "The cell(" + getCellTxtCoordinate(i, j) + ") is missing.");
					return -1;
				}
				columns.add(c);
				
				if (i == j) {
					rows.add(c);
				} else {
					c = findCellByXY(j, i);
					if (c == null) {
						SysLogger.parserErr(fileFullname, "The cell(" + getCellTxtCoordinate(j, i) + ") is missing.");
						return -1;
					}
					rows.add(c);
				}
			}
		}
		return 0;
	}
	
	/*
	 *  check if the layout is validity
	 *  case1: cells number should be equal to the number of the grids of layout
	 *  case2: no same cells
	 *  case3: the cage should be validity. all the cells of the cage are connected.
	 */
	public int syntaxChecker() {
		int cellNum = 0;
		
		// case1
		for (int i = 0; i < cages.size(); i++) {
			cellNum += cages.get(i).cageSize;
		}
		if (cellNum != (gameSize * gameSize)) {
			SysLogger.parserErr(fileFullname, "Too less or too many cells. Cells in total: "
					+ cellNum + " but GameSize is: " + gameSize);
			return -1;
		}
		
		// case2
		if (sortCells() != 0) {
			return -1;
		}
		
		// case3
		for (int i = 0; i < cages.size(); i++) {
			if (cages.get(i).cageSize < 2) {
				continue;
			}

			ArrayList<Cell> cells = cages.get(i).cells;
			
			for (int j = 0; j < cells.size(); j++) {
				// TODO: skip the one that already compared.
				
				Cell c = cells.get(j);
				boolean ifConnected = false;
				StringBuffer cellMsg = new StringBuffer();
				
				cellMsg.append("(" + getCellTxtCoordinate(c.x, c.y) + ") ");
				for (int k = 0; k < cells.size(); k++) {
					if (k == j) {
						continue;						
					}
					if (c.x == cells.get(k).x || c.y == cells.get(k).y) {
						ifConnected = true;
						break;
					}
					cellMsg.append("(" + getCellTxtCoordinate(cells.get(k).x, cells.get(k).y) + ") ");
				}
				if (!ifConnected) {
					SysLogger.parserErr(fileFullname, "Cells " + cellMsg.toString() 
							+ "cannot make up a cage.");
					return -1;
				}
			}
		}
		return 0;
	}
	
	// add data to cells for printing
	public int addDataToCells() {
		int pad = 1;
		
		for (int i = 0; i < cages.size(); i++) {
			ArrayList<Cell> c = cages.get(i).cells;
			for (int j = 0; j < c.size(); j++) {
				c.get(j).data = pad;
			}
			pad++;
		}
		return 0;
	}
	
	// check if there is a right solution
	public int checkSolution() {
		// if all the cells have value
		for (int i = 0; i < rows.size(); i++) {
			if (rows.get(i).value == 0) {
				return 1;
			}
		}
		
		// if each number is used only once by row and column
		// TODO: the following algorithm needs to be approved
		int numSum = 0, rowSum = 0, colSum = 0, numMulSum = 1, rowMulSum = 1, colMulSum = 1;
		
		for (int i = 0; i < gameSize; i++) {
			numSum += i + 1;
			numMulSum *= i + 1;
		}
		for (int i = 0; i < gameSize; i++) {
			rowSum = 0;
			colSum = 0;
			rowMulSum = 1; 
			colMulSum = 1;
			for (int j = 0; j < gameSize; j++) {
				rowSum += rows.get(i * gameSize + j).value;
				rowMulSum *= rows.get(i * gameSize + j).value;
				colSum += rows.get(j * gameSize + i).value;
				colMulSum *= rows.get(j * gameSize + i).value;
			}
			if (numSum != rowSum || numSum != colSum 
					|| numMulSum != rowMulSum || numMulSum != colMulSum) {
				return -1;
			}
		}
		
		// if the cells values satisfy the cage formula.
		for (int i = 0; i < cages.size(); i++) {
			Cage c = cages.get(i);
			int ret = 0;
			
			switch (c.operator) {
			case Cage.OP_EQU:
				if (c.cells.get(0).value != c.targetNumber) {
					return -1;
				}
				break;
			case Cage.OP_ADD:
			case Cage.OP_MUL:
				for (int j = 0; j < c.cells.size(); j++) {
					if (c.operator == Cage.OP_ADD) {
						ret += c.cells.get(j).value;
					} else {
						if (ret == 0) {
							ret = 1;
						}
						ret *= c.cells.get(j).value;
					}
				}
				if (ret != c.targetNumber) {
					return -1;
				}
				break;
			case Cage.OP_SUB:				
			case Cage.OP_DIV:
				int indexOfMaxOne = 0;
				
				ret = c.targetNumber;
				for (int j = 0; j < c.cells.size() - 1; j++) {
					if (c.cells.get(j).value > c.cells.get(j + 1).value) {
						indexOfMaxOne = j;
					} else {
						indexOfMaxOne = j + 1;
					}
				}
				for (int j = 0; j < c.cells.size(); j++) {
					if (j == indexOfMaxOne) {
						continue;
					}
					if (c.operator == Cage.OP_SUB) {
						ret += c.cells.get(j).value;
					} else {
						ret *= c.cells.get(j).value;
					}
				}
				if (ret != c.cells.get(indexOfMaxOne).value) {
					return -1;
				}
				break;
			}		
		}
		return 0;
	}
	
	// set a cell value
	public int setCellValue(String cellName, int val, int type) {
		char ch = cellName.charAt(0);
		int index = 0;
		
		// get the index
		SysLogger.info("setCellVal: " + cellName + ":" + val);
		if (ch < 'A' || ch > 'A' + gameSize - 1) {
			return -1;
		}
		index = (ch - 'A') * gameSize;
		
		ch = cellName.charAt(1);
		if (ch < '1' || ch > '1' + gameSize - 1) {
			return -1;
		}
		index += ch - '1';
		
		if (index >= rows.size()) {
			return -2;
		}
		
		// set value to the cell
		if (type == 1) {
			rows.get(index).value = val;
		} else {
			// TODO: if there is no value, set the value instead of possible value
			if (val == 0) {
				rows.get(index).possibleValues.clear();
			} else {
				if (rows.get(index).possibleValues.size() > 5) {
					rows.get(index).possibleValues.remove(0);   // remove the first one
				}
				rows.get(index).possibleValues.add(val);
			}
		}
		
		// refresh the data which will be used by UI
		rows.get(index).possValues = "";
		for (int i = 0; i < rows.get(index).possibleValues.size(); i++) {
			rows.get(index).possValues += rows.get(index).possibleValues.get(i);
		}
		
		return 0;
	}
	
}
